package com.terrier.net.protocol
{
    import com.terrier.net.IWxSocketStrategy;
    import com.terrier.net.WxNetPacket;
    import com.terrier.net.WxSocket;
    import com.terrier.net.events.WxSocketEvent;
    import com.terrier.utils.WxHashMap;

    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IOErrorEvent;
    import flash.events.SecurityErrorEvent;
    import flash.utils.Dictionary;
    import flash.utils.getQualifiedClassName;

    /**
     * 在建立网络连接后调度。
     * @eventType flash.events.Event.CONNECT
     */
    [Event(name = "connect", type = "flash.events.Event")]

    /**
     * 在服务器关闭套接字连接时调度。 仅在服务器关闭连接时调度 close 事件；在调用 close() 方法时不调度该事件。
     * @eventType flash.events.Event.CLOSE
     */
    [Event(name = "close", type = "flash.events.Event")]

    /**
     * 在出现输入/输出错误并导致发送或加载操作失败时调度。
     * @eventType flash.events.IOErrorEvent.IO_ERROR
     */
    [Event(name = "ioError", type = "flash.events.IOErrorEvent")]

    /**
     * 若对 Socket.connect() 的调用尝试连接到调用方安全沙箱外部的服务器或端口号低于 1024 端口，则进行调度。
     * @eventType flash.events.SecurityErrorEvent.SECURITY_ERROR
     */
    [Event(name = "securityError", type = "flash.events.SecurityErrorEvent")]


    /**
     * 网络服务类, 使用Socket进行网络通信.
     * @author Jarvis.weng
     */
    public class WxRemoteService extends EventDispatcher
    {
        private var _socket:WxSocket;

        private var _c2sMsgIDMap:Dictionary; // C2S 消息ID映射表  {key:Class, value:ID};

        private var _c2sMsgClassMap:Dictionary; // C2S 消息ID映射表 {key:ID, value:Class};

        private var _s2cMsgIDMap:Dictionary; // S2C 消息ID映射表 {key:Class, value:ID};

        private var _processorMap:WxHashMap; // S2C 消息处理器映射表 {key:ID, value:Processor};

        private var _cacheDataMap:WxHashMap;

        /**
         * 构造函数
         * @param strategy 处理消息包解析的对象
         */
        public function WxRemoteService(strategy:IWxSocketStrategy = null)
        {
            _socket = new WxSocket(strategy);
            _socket.addEventListener(Event.CONNECT, connectHandler);
            _socket.addEventListener(Event.CLOSE, closeHandler);
            _socket.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
            _socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
            _socket.addEventListener(WxSocketEvent.SOCKET_DATA, socketDataHandler);

            _c2sMsgIDMap = new Dictionary();
            _c2sMsgClassMap = new Dictionary();

            _s2cMsgIDMap = new Dictionary();
            _processorMap = new WxHashMap();

            _cacheDataMap = new WxHashMap();
        }

        /**
         * 指示此对象目前是否已连接。 如果该套接字当前已连接，则对此属性的调用将返回值 true，否则将返回 false。
         * @return
         */
        public function get connected():Boolean
        {
            return _socket.connected;
        }

        /**
         * 指示建立连接时需等待的毫秒数。 如果在指定时间内未成功建立连接，则连接失败。默认值是 20,000（20 秒）。
         * @return
         */
        public function get timeout():uint
        {
            return _socket.timeout;
        }

        /**
         * @private
         */
        public function set timeout(value:uint):void
        {
            _socket.timeout = value;
        }

        /**
         * 将套接字连接到指定的主机和端口。
         * 如果连接立即失败，则调度一个事件或引发一个异常。如果指定了主机，则调度一个错误事件；如果未指定主机，则引发异常。
         * 另外，连接的状态由事件来报告。 如果已连接上套接字，则现有的连接将首先关闭。
         * @param host 要连接到的主机的名称或 IP 地址。如果未指定主机，则要连接的主机为执行调用的 文件所在的主机。
         * @param port 要连接的端口号
         */
        public function connect(host:String, port:int):void
        {
            _socket.connect(host, port);
        }

        /**
         * 关闭套接字。 在调用 close() 方法之后，将无法读取或写入任何数据。
         * 仅在服务器关闭连接时调度 close 事件；在调用 close() 方法时不调度该事件。
         * 可通过再次对 Socket 对象调用 connect() 方法来重复使用该对象。
         */
        public function close():void
        {
            _socket.close();
        }

        /**
         * 发送消息包
         * @param msg 消息包
         */
        public function sendMessage(msg:IWxMessage):void
        {
            var data:WxNetPacket = new WxNetPacket(getC2SMsgID(msg));

            msg.writeTo(data);

            _socket.send(data);
        }

        /**
         * 添加消息包处理函数
         * @param msgClass 消息包的类型
         * @param handler 处理函数
         * @param priority 事件侦听器的优先级。 优先级由一个带符号的 32 位整数指定。 数字越大，优先级越高。 优先级为 n 的所有侦听器会在优先级为 n -1 的侦听器之前得到处理。 如果两个或更多个侦听器共享相同的优先级，则按照它们的添加顺序进行处理。 默认优先级为 0。
         */
        public function addMessageHandler(msgClass:Class, handler:Function, priority:int = 0):void
        {
            var msgID:uint = getS2CMsgID(msgClass);
            var processor:MessageProcessor = _processorMap.getValue(msgID);
            processor.addHandler(handler, priority);

            if (cacheDataLength > 0 && processor.length == 1)
            {
                processCacheData(msgID, processor);
            }
        }

        /**
         * 注册双通道(C2S,S2C)的消息包类型
         * @param msgClass 消息包类型
         * @param id 消息包对应的ID
         * @throws Error 如果消息包类型已注册,则引发异常
         * @throws Error 如果消息包ID已注册,则引发异常
         */
        public function registerDualChannelMessage(msgClass:Class, id:uint):void
        {
            registerC2SMessage(msgClass, id);
            registerS2CMessage(msgClass, id);
        }

        /**
         * 注册客户端发送给服务端的消息包类型
         * @param msgClass 消息包类型
         * @param id 消息包ID
         * @throws Error 如果消息包类型已注册,则引发异常
         * @throws Error 如果消息包ID已注册,则引发异常
         */
        public function registerC2SMessage(msgClass:Class, id:uint):void
        {
            var className:String = getQualifiedClassName(msgClass);

            if (_c2sMsgIDMap[className] != null)
            {
                throw new Error("The Message type <" + className + "> has been registered.");
            }

            if (_c2sMsgClassMap[id] != null)
            {
                throw new Error("The Message id <" + id + "> has been registered.");
            }

            _c2sMsgIDMap[className] = id;
            _c2sMsgClassMap[id] = msgClass
        }

        /**
         * 注册服务端发送给客户端的消息包类型
         * @param msgClass 消息包类型
         * @param id 消息包ID
         * @throws Error 如果消息包类型已注册,则引发异常
         * @throws Error 如果消息包ID已注册,则引发异常
         */
        public function registerS2CMessage(msgClass:Class, id:uint):void
        {
            var className:String = getQualifiedClassName(msgClass);

            if (_s2cMsgIDMap[className] != null)
            {
                throw new Error("The Message type<" + className + ">  has been registered.");
            }

            if (_processorMap.has(id))
            {
                throw new Error("The Message id<" + id + ">  has been registered.");
            }

            _s2cMsgIDMap[className] = id;

            var processor:MessageProcessor = new MessageProcessor(msgClass);
            _processorMap.add(id, processor);
        }

        /**
         * 获取客户端发送给服务器端的消息ID
         * @param value 消息的类型(Class)或者消息的实例对象(Object)
         * @return 对应的消息ID
         * @throws Error 如果消息类型还未注册,将会引发异常.
         */
        public function getC2SMsgID(value:*):uint
        {
            var className:String = getQualifiedClassName(value);

            if (_c2sMsgIDMap[className] == null)
            {
                throw new Error("This Message type <" + className + "> has not been registered yet.");
            }

            return _c2sMsgIDMap[className];
        }

        /**
         * 获得服务器发送给客户端的消息ID
         * @param value 消息的类型(Class)或者消息的实例对象(Object)
         * @return 对应的消息ID
         * @throws Error 如果消息类型未注册,将会引发异常
         */
        public function getS2CMsgID(value:*):uint
        {
            var className:String = getQualifiedClassName(value);

            if (_s2cMsgIDMap[className] == null)
            {
                throw new Error("This Message type<" + className + "> has not been registered yet.");
            }

            return _s2cMsgIDMap[className];
        }

        /**
         * 如果底层接收到未知ID的消息包,则调用该方法
         * @param data
         */
        protected function debugUnknownData(data:WxNetPacket):void
        {
            // subclass could override this method to implement its own behavior
            cacheSocketData(data);
        }

        /**
         * 如果底层接收到的消息包没有对应的处理函数,则调用该方法
         * @param data
         */
        protected function debugNoHandlerData(data:WxNetPacket):void
        {
            // subclass could override this method to implement its own behavior
            cacheSocketData(data);
        }

        //--------------------------------------
        //
        // Private Methods
        //
        //--------------------------------------

        private function cacheSocketData(data:WxNetPacket):void
        {
            var dataArr:Array = _cacheDataMap.getValue(data.id);
            if (dataArr == null)
            {
                dataArr = new Array();
                _cacheDataMap.add(data.id, dataArr);
            }
            dataArr.push(data);
        }

        private function get cacheDataLength():int
        {
            return _cacheDataMap.length;
        }

        private function processCacheData(msgID:uint, processor:MessageProcessor):void
        {
            var dataArr:Array = _cacheDataMap.remove(msgID);
            if (dataArr == null)
            {
                return;
            }
            for (var i:int = 0; i < dataArr.length; ++i)
            {
                var data:WxNetPacket = dataArr[i];
                processor.execute(data);
            }
        }

        //--------------------------------------
        //
        // Event Handlers
        //
        //--------------------------------------

        private function connectHandler(e:Event):void
        {
            dispatchEvent(e);
        }

        private function closeHandler(e:Event):void
        {
            dispatchEvent(e);
        }

        private function ioErrorHandler(e:IOErrorEvent):void
        {
            dispatchEvent(e);
        }

        private function securityErrorHandler(e:SecurityErrorEvent):void
        {
            dispatchEvent(e);
        }

        /**
         * @private
         */
        protected function socketDataHandler(e:WxSocketEvent):void
        {
            var data:WxNetPacket = e.data;
            var processor:MessageProcessor = _processorMap.getValue(data.id);

            if (processor == null)
            {
                debugUnknownData(data);
                return;
            }

            if (processor.length == 0)
            {
                debugNoHandlerData(data);
                return;
            }

            processor.execute(data);
        }
    }
}
import com.terrier.net.WxNetPacket;
import com.terrier.net.protocol.IWxMessage;
import com.terrier.utils.WxHashMap;

class MessageProcessor
{
    private var _msgClass:Class;

    private var _handlerMap:WxHashMap;

    private var _handlers:Array;

    private var _priorityIndexs:Array;

    /**
     *
     * @param msgClass
     */
    public function MessageProcessor(msgClass:Class)
    {
        _msgClass = msgClass;
        _handlerMap = new WxHashMap();
        resetHandlerArray();
    }

    /**
     *
     * @return
     */
    public function get length():int
    {
        return _handlers.length;
    }

    /**
     *
     * @param handler
     * @param priority
     */
    public function addHandler(handler:Function, priority:int):void
    {
        _handlerMap.add(handler, priority);
        resetHandlerArray();
    }

    /**
     *
     * @param data
     */
    public function execute(data:WxNetPacket):void
    {
        var len:int = _priorityIndexs.length - 1;
        for (var i:int = 0; i <= len; ++i)
        {
            var msg:IWxMessage = new _msgClass();
            msg.readFrom(i == len ? data : data.clone());
            var handler:Function = _handlers[_priorityIndexs[i]];
            var converter:* = msg;
            handler(converter);
        }
    }

    private function resetHandlerArray():void
    {
        _priorityIndexs = _handlerMap.values.sort(Array.RETURNINDEXEDARRAY | Array.DESCENDING);
        _handlers = _handlerMap.keys;
    }
}
